"""
Additional objects like axes, legends etc..
"""
from __future__ import division, print_function
from vtkplotter.colors import printc, getColor
from vtkplotter.actors import Assembly, Actor
from vtkplotter.utils import precision, mag, isSequence
import vtkplotter.shapes as shapes
import vtkplotter.settings as settings
import vtkplotter.docs as docs
import numpy as np
import vtk

__doc__ = (
    """
Additional objects like axes, legends etc..
"""
    + docs._defs
)

__all__ = [
        "addLight",
        "addScalarBar",
        "addScalarBar3D",
        "addSlider2D",
        "addSlider3D",
        "addButton",
        "addCutterTool",
        "addIcon",
        "addAxes",
        "addLegend",
        ]


#####################################################################
def addLight(
    pos,
    focalPoint=(0, 0, 0),
    deg=180,
    c='white',
    intensity=0.4,
    removeOthers=False,
    showsource=False,
):
    """
    Generate a source of light placed at pos, directed to focal point.
    Returns a ``vtkLight`` object.

    :param focalPoint: focal point, if this is a ``vtkActor`` use its position.
    :type fp: vtkActor, list
    :param deg: aperture angle of the light source
    :param c: set light color
    :param float intensity: intensity between 0 and 1.
    :param bool removeOthers: remove all other lights in the scene
    :param bool showsource: if `True`, will show a representation
                            of the source of light as an extra Actor

    .. hint:: |lights.py|_
    """
    if isinstance(focalPoint, vtk.vtkActor):
        focalPoint = focalPoint.GetPosition()
    light = vtk.vtkLight()
    light.SetLightTypeToSceneLight()
    light.SetPosition(pos)
    light.SetPositional(True)
    light.SetConeAngle(deg)
    light.SetFocalPoint(focalPoint)
    light.SetIntensity(intensity)
    light.SetColor(getColor(c))
    #light.SetShadowAttenuation(0.1) # doesn't work
    if showsource:
        lightActor = vtk.vtkLightActor()
        lightActor.SetLight(light)
        settings.plotter_instance.renderer.AddViewProp(lightActor)
    if removeOthers:
        settings.plotter_instance.renderer.RemoveAllLights()
    settings.plotter_instance.renderer.AddLight(light)
    return light


#####################################################################
def addScalarBar(actor,
                 pos=(0.8,0.05),
                 title="",
                 titleXOffset=0,
                 titleYOffset=15,
                 titleFontSize=12,
                 nlabels=10,
                 c=None,
                 horizontal=False,
                 vmin=None, vmax=None,
                 ):
    """Add a 2D scalar bar for the specified actor.

    .. hint:: |mesh_coloring| |mesh_coloring.py|_ |scalarbars.py|_
    """
    vp = settings.plotter_instance

    if not hasattr(actor, "mapper"):
        printc("~times addScalarBar(): input is invalid,", type(actor), c=1)
        return None

    if vp and vp.renderer:
        c = (0.9, 0.9, 0.9)
        if np.sum(vp.renderer.GetBackground()) > 1.5:
            c = (0.1, 0.1, 0.1)
        if isinstance(actor.scalarbar, vtk.vtkActor):
            vp.renderer.RemoveActor(actor.scalarbar)
        elif isinstance(actor.scalarbar, Assembly):
            for a in actor.scalarbar.getActors():
                vp.renderer.RemoveActor(a)
    if c is None: c = 'gray'

    if isinstance(actor, Actor):
        lut = actor.mapper().GetLookupTable()
        if not lut:
            return None
        vtkscalars = actor._polydata.GetPointData().GetScalars()
        if vtkscalars is None:
            vtkscalars = actor._polydata.GetCellData().GetScalars()
        if not vtkscalars:
            return None

        rng = list(vtkscalars.GetRange())
        if vmin is not None: rng[0] = vmin
        if vmax is not None: rng[1] = vmax
        actor.mapper().SetScalarRange(rng)

    elif isinstance(actor, 'Volume'):
        # to be implemented
        pass

    c = getColor(c)
    sb = vtk.vtkScalarBarActor()
    sb.SetLookupTable(lut)
    if title:
        titprop = vtk.vtkTextProperty()
        titprop.BoldOn()
        titprop.ItalicOff()
        titprop.ShadowOff()
        titprop.SetColor(c)
        titprop.SetVerticalJustificationToTop()
        sb.SetTitle(title)
        sb.SetVerticalTitleSeparation(titleYOffset)
#        sb.SetHorizontalTitleSeparation(titleXOffset)
        sb.SetTitleTextProperty(titprop)

    if vtk.vtkVersion().GetVTKMajorVersion() > 7:
        sb.UnconstrainedFontSizeOn()
        sb.FixedAnnotationLeaderLineColorOff()
        sb.DrawAnnotationsOn()
        sb.DrawTickLabelsOn()
    sb.SetMaximumNumberOfColors(256)

    if horizontal:
        sb.SetOrientationToHorizontal()
        sb.SetNumberOfLabels(int((nlabels-1)/2.))
        sb.SetTextPositionToSucceedScalarBar()
        sb.SetPosition(pos)
        sb.SetMaximumWidthInPixels(1000)
        sb.SetMaximumHeightInPixels(50)
    else:
        sb.SetNumberOfLabels(nlabels)
        sb.SetTextPositionToPrecedeScalarBar()
        sb.SetPosition(pos[0]+0.07, pos[1])
        sb.SetMaximumWidthInPixels(80)
        sb.SetMaximumHeightInPixels(500)

    sctxt = sb.GetLabelTextProperty()
    sctxt.SetColor(c)
    sctxt.SetShadow(0)
    sctxt.SetFontFamily(0)
    sctxt.SetItalic(0)
    sctxt.SetBold(0)
    sctxt.SetFontSize(titleFontSize)
    sb.PickableOff()
    actor.scalarbar = sb
    return sb


#####################################################################
def addScalarBar3D(
    obj,
    pos=(0, 0, 0),
    normal=(0, 0, 1),
    sx=0.1,
    sy=2,
    title='',
    titleXOffset = -1.5,
    titleYOffset = 0.0,
    titleSize =  1.5,
    titleRotation = 0.0,
    nlabels=9,
    prec=3,
    labelOffset = 0.4,
    c=None,
    alpha=1,
    cmap=None,
):
    """Draw a 3D scalar bar.

    ``obj`` input can be:
        - a list of numbers,
        - a list of two numbers in the form `(min, max)`,
        - a ``vtkActor`` already containing a set of scalars associated to vertices or cells,
        - if ``None`` the last actor in the list of actors will be used.

    :param float sx: thickness of scalarbar
    :param float sy: length of scalarbar
    :param str title: scalar bar title
    :param float titleXOffset: horizontal space btw title and color scalarbar
    :param float titleYOffset: vertical space offset
    :param float titleSize: size of title wrt numeric labels
    :param float titleRotation: title rotation in degrees
    :param int nlabels: number of numeric labels
    :param int prec: number of significant digits
    :param float labelOffset: space btw numeric labels and scale
    :param str cmap: specify cmap to be used

    .. hint:: |scalarbars| |scalarbars.py|_
    """
    vp = settings.plotter_instance
    if vp and c is None:  # automatic black or white
        c = (0.8, 0.8, 0.8)
        if np.sum(getColor(vp.backgrcol)) > 1.5:
            c = (0.2, 0.2, 0.2)
    if c is None: c = 'gray'
    c = getColor(c)

    if isinstance(obj, Actor):
        if cmap is None:
            lut = obj.mapper().GetLookupTable()
            if not lut:
                print("Error in ScalarBar3D: actor has no active scalar array.", [obj])
                return None
        else:
            lut = cmap
        vmin,vmax = obj.mapper().GetScalarRange()

    elif isSequence(obj):
        vmin, vmax = np.min(obj), np.max(obj)
    else:
        print("Error in ScalarBar3D(): input must be Actor or list.", type(obj))
        raise RuntimeError()

    # build the color scale part
    scale = shapes.Grid([-sx *labelOffset, 0, 0], c=c, alpha=alpha,
                        sx=sx, sy=sy, resx=1, resy=256)
    scale.lw(0).wireframe(False)
    cscals = scale.cellCenters()[:, 1]
    scale.cellColors(cscals, lut, alpha)
    scale.lighting(ambient=1, diffuse=0, specular=0, specularPower=0)

    # build text
    tlabs = np.linspace(vmin, vmax, num=nlabels, endpoint=True)
    tacts = []
    for i, t in enumerate(tlabs):
        tx = precision(t, prec, vrange=vmax-vmin)
        y = -sy / 1.98 + sy * i / (nlabels - 1)
        a = shapes.Text(tx, pos=[sx*labelOffset, y, 0], s=sy/50, c=c, alpha=alpha, depth=0)
        a.lighting(ambient=1, diffuse=0, specular=0, specularPower=0)
        a.PickableOff()
        tacts.append(a)

    # build title
    if title:
        t = shapes.Text(title, (0,0,0), s=sy/50*titleSize, c=c, alpha=alpha, depth=0,
                        justify='centered')
        t.RotateZ(90+titleRotation)
        t.pos(sx*titleXOffset,titleYOffset,0)
        t.lighting(ambient=1, diffuse=0, specular=0, specularPower=0)
        t.PickableOff()
        tacts.append(t)

    sact = Assembly([scale] + tacts)
    normal = np.array(normal) / np.linalg.norm(normal)
    theta = np.arccos(normal[2])
    phi = np.arctan2(normal[1], normal[0])
    sact.RotateZ(np.rad2deg(phi))
    sact.RotateY(np.rad2deg(theta))
    sact.SetPosition(pos)
    sact.PickableOff()
    if isinstance(obj, Actor):
        obj.scalarbar = sact
    return sact

#####################################################################
def addSlider2D(sliderfunc, xmin, xmax, value=None, pos=4,
                title='', c=None, showValue=True):
    """Add a slider widget which can call an external custom function.

    :param sliderfunc: external function to be called by the widget
    :param float xmin:  lower value
    :param float xmax:  upper value
    :param float value: current value
    :param list pos:  position corner number: horizontal [1-4] or vertical [11-14]
                        it can also be specified by corners coordinates [(x1,y1), (x2,y2)]
    :param str title: title text
    :param bool showValue:  if true current value is shown

    |sliders| |sliders.py|_
    """
    vp = settings.plotter_instance
    if c is None:  # automatic black or white
        c = (0.8, 0.8, 0.8)
        if np.sum(getColor(vp.backgrcol)) > 1.5:
            c = (0.2, 0.2, 0.2)
    c = getColor(c)

    if value is None or value < xmin:
        value = xmin

    sliderRep = vtk.vtkSliderRepresentation2D()
    sliderRep.SetMinimumValue(xmin)
    sliderRep.SetMaximumValue(xmax)
    sliderRep.SetValue(value)
    sliderRep.SetSliderLength(0.015)
    sliderRep.SetSliderWidth(0.025)
    sliderRep.SetEndCapLength(0.0015)
    sliderRep.SetEndCapWidth(0.0125)
    sliderRep.SetTubeWidth(0.0075)
    sliderRep.GetPoint1Coordinate().SetCoordinateSystemToNormalizedDisplay()
    sliderRep.GetPoint2Coordinate().SetCoordinateSystemToNormalizedDisplay()
    if isSequence(pos):
        sliderRep.GetPoint1Coordinate().SetValue(pos[0][0], pos[0][1])
        sliderRep.GetPoint2Coordinate().SetValue(pos[1][0], pos[1][1])
    elif pos == 1:  # top-left horizontal
        sliderRep.GetPoint1Coordinate().SetValue(0.04, 0.96)
        sliderRep.GetPoint2Coordinate().SetValue(0.45, 0.96)
    elif pos == 2:
        sliderRep.GetPoint1Coordinate().SetValue(0.55, 0.96)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.96)
    elif pos == 3:
        sliderRep.GetPoint1Coordinate().SetValue(0.04, 0.04)
        sliderRep.GetPoint2Coordinate().SetValue(0.45, 0.04)
    elif pos == 4:  # bottom-right
        sliderRep.GetPoint1Coordinate().SetValue(0.55, 0.04)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.04)
    elif pos == 5:  # bottom margin horizontal
        sliderRep.GetPoint1Coordinate().SetValue(0.04, 0.04)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.04)
    elif pos == 11:  # top-left vertical
        sliderRep.GetPoint1Coordinate().SetValue(0.04, 0.54)
        sliderRep.GetPoint2Coordinate().SetValue(0.04, 0.9)
    elif pos == 12:
        sliderRep.GetPoint1Coordinate().SetValue(0.96, 0.54)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.9)
    elif pos == 13:
        sliderRep.GetPoint1Coordinate().SetValue(0.04, 0.1)
        sliderRep.GetPoint2Coordinate().SetValue(0.04, 0.54)
    elif pos == 14:  # bottom-right vertical
        sliderRep.GetPoint1Coordinate().SetValue(0.96, 0.1)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.54)
    elif pos == 15:  # right margin vertical
        sliderRep.GetPoint1Coordinate().SetValue(0.96, 0.1)
        sliderRep.GetPoint2Coordinate().SetValue(0.96, 0.9)

    if showValue:
        if isinstance(xmin, int) and isinstance(xmax, int):
            frm = "%0.0f"
        else:
            frm = "%0.1f"
        sliderRep.SetLabelFormat(frm)  # default is '%0.3g'
        sliderRep.GetLabelProperty().SetShadow(0)
        sliderRep.GetLabelProperty().SetBold(0)
        sliderRep.GetLabelProperty().SetOpacity(1)
        sliderRep.GetLabelProperty().SetColor(c)
        if isinstance(pos, int) and pos > 10:
            sliderRep.GetLabelProperty().SetOrientation(90)
    else:
        sliderRep.ShowSliderLabelOff()
    sliderRep.GetTubeProperty().SetColor(c)
    sliderRep.GetTubeProperty().SetOpacity(0.6)
    sliderRep.GetSliderProperty().SetColor(c)
    sliderRep.GetSelectedProperty().SetColor(0.8, 0, 0)
    sliderRep.GetCapProperty().SetColor(c)

    if title:
        sliderRep.SetTitleText(title)
        sliderRep.SetTitleHeight(0.012)
        sliderRep.GetTitleProperty().SetShadow(0)
        sliderRep.GetTitleProperty().SetColor(c)
        sliderRep.GetTitleProperty().SetOpacity(1)
        sliderRep.GetTitleProperty().SetBold(0)
        if not isSequence(pos):
            if isinstance(pos, int) and pos > 10:
                sliderRep.GetTitleProperty().SetOrientation(90)
        else:
            if abs(pos[0][0] - pos[1][0]) < 0.1:
                sliderRep.GetTitleProperty().SetOrientation(90)

    sliderWidget = vtk.vtkSliderWidget()
    sliderWidget.SetInteractor(vp.interactor)
    sliderWidget.SetAnimationModeToJump()
    sliderWidget.SetRepresentation(sliderRep)
    sliderWidget.AddObserver("InteractionEvent", sliderfunc)
    sliderWidget.EnabledOn()
    vp.sliders.append([sliderWidget, sliderfunc])
    return sliderWidget

#####################################################################
def addSlider3D(
    sliderfunc,
    pos1,
    pos2,
    xmin,
    xmax,
    value=None,
    s=0.03,
    title="",
    rotation=0,
    c=None,
    showValue=True,
):
    """Add a 3D slider widget which can call an external custom function.

    :param sliderfunc: external function to be called by the widget
    :param list pos1: first position coordinates
    :param list pos2: second position coordinates
    :param float xmin:  lower value
    :param float xmax:  upper value
    :param float value: initial value
    :param float s: label scaling factor
    :param str title: title text
    :param c: slider color
    :param float rotation: title rotation around slider axis
    :param bool showValue: if True current value is shown

    |sliders3d| |sliders3d.py|_
    """
    vp = settings.plotter_instance
    if c is None:  # automatic black or white
        c = (0.8, 0.8, 0.8)
        if np.sum(getColor(vp.backgrcol)) > 1.5:
            c = (0.2, 0.2, 0.2)
    else:
        c = getColor(c)

    if value is None or value < xmin:
        value = xmin

    t = 1.5 / np.sqrt(mag(np.array(pos2) - pos1))  # better norm

    sliderRep = vtk.vtkSliderRepresentation3D()
    sliderRep.SetMinimumValue(xmin)
    sliderRep.SetValue(value)
    sliderRep.SetMaximumValue(xmax)

    sliderRep.GetPoint1Coordinate().SetCoordinateSystemToWorld()
    sliderRep.GetPoint2Coordinate().SetCoordinateSystemToWorld()
    sliderRep.GetPoint1Coordinate().SetValue(pos2)
    sliderRep.GetPoint2Coordinate().SetValue(pos1)

    sliderRep.SetSliderWidth(0.03 * t)
    sliderRep.SetTubeWidth(0.01 * t)
    sliderRep.SetSliderLength(0.04 * t)
    sliderRep.SetSliderShapeToCylinder()
    sliderRep.GetSelectedProperty().SetColor(1, 0, 0)
    sliderRep.GetSliderProperty().SetColor(np.array(c) / 2)
    sliderRep.GetCapProperty().SetOpacity(0)

    sliderRep.SetRotation(rotation)

    if not showValue:
        sliderRep.ShowSliderLabelOff()

    sliderRep.SetTitleText(title)
    sliderRep.SetTitleHeight(s * t)
    sliderRep.SetLabelHeight(s * t * 0.85)

    sliderRep.GetTubeProperty()
    sliderRep.GetTubeProperty().SetColor(c)

    sliderWidget = vtk.vtkSliderWidget()
    sliderWidget.SetInteractor(vp.interactor)
    sliderWidget.SetRepresentation(sliderRep)
    sliderWidget.SetAnimationModeToJump()
    sliderWidget.AddObserver("InteractionEvent", sliderfunc)
    sliderWidget.EnabledOn()
    vp.sliders.append([sliderWidget, sliderfunc])
    return sliderWidget

#####################################################################
def addButton(
    fnc,
    states=("On", "Off"),
    c=("w", "w"),
    bc=("dg", "dr"),
    pos=(20, 40),
    size=24,
    font="arial",
    bold=False,
    italic=False,
    alpha=1,
    angle=0,
):
    """Add a button to the renderer window.

    :param list states: a list of possible states ['On', 'Off']
    :param c:      a list of colors for each state
    :param bc:     a list of background colors for each state
    :param pos:    2D position in pixels from left-bottom corner
    :param size:   size of button font
    :param str font:   font type (arial, courier, times)
    :param bool bold:   bold face (False)
    :param bool italic: italic face (False)
    :param float alpha:  opacity level
    :param float angle:  anticlockwise rotation in degrees

    |buttons| |buttons.py|_
    """
    vp = settings.plotter_instance
    if not vp.renderer:
        printc("~timesError: Use addButton() after rendering the scene.", c=1)
        return
    bu = Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle)
    vp.renderer.AddActor2D(bu.actor)
    vp.window.Render()
    vp.buttons.append(bu)
    return bu


def addCutterTool(actor):
    """Create handles to cut away parts of a mesh.

    |cutter| |cutter.py|_
    """
    if isinstance(actor, vtk.vtkVolume):
        return _addVolumeCutterTool(actor)
    elif isinstance(actor, vtk.vtkImageData):
        from vtkplotter import Volume
        return _addVolumeCutterTool(Volume(actor))

    vp = settings.plotter_instance
    if not vp.renderer:
        save_int = vp.interactive
        vp.show(interactive=0)
        vp.interactive = save_int

    vp.clickedActor = actor
    apd = actor.polydata()

    planes = vtk.vtkPlanes()
    planes.SetBounds(apd.GetBounds())

    clipper = vtk.vtkClipPolyData()
    clipper.GenerateClipScalarsOn()
    clipper.SetInputData(apd)
    clipper.SetClipFunction(planes)
    clipper.InsideOutOn()
    clipper.GenerateClippedOutputOn()
    clipper.Update()
    cpoly = clipper.GetOutput()

    act0 = Actor(cpoly, alpha=actor.alpha()) # the main cut part
    act0.mapper().SetLookupTable(actor.mapper().GetLookupTable())
    act0.mapper().SetScalarRange(actor.mapper().GetScalarRange())

    act1 = Actor()
    act1.mapper().SetInputConnection(clipper.GetClippedOutputPort()) # needs OutputPort??
    act1.alpha(0.04).color((0.5,0.5,0.5)).wireframe()

    vp.renderer.RemoveActor(actor)
    vp.renderer.AddActor(act0)
    vp.renderer.AddActor(act1)
    vp.renderer.ResetCamera()

    def selectPolygons(vobj, event):
        vobj.GetPlanes(planes)

    boxWidget = vtk.vtkBoxWidget()
    boxWidget.OutlineCursorWiresOn()
    boxWidget.GetSelectedOutlineProperty().SetColor(1, 0, 1)
    boxWidget.GetOutlineProperty().SetColor(0.2, 0.2, 0.2)
    boxWidget.GetOutlineProperty().SetOpacity(0.8)
    boxWidget.SetPlaceFactor(1.05)
    boxWidget.SetInteractor(vp.interactor)
    boxWidget.SetInputData(apd)
    boxWidget.PlaceWidget()
    boxWidget.AddObserver("InteractionEvent", selectPolygons)
    boxWidget.On()

    vp.cutterWidget = boxWidget
    vp.clickedActor = act0
    if actor in vp.actors:
        ia = vp.actors.index(actor)
        vp.actors[ia] = act0

    printc("Mesh Cutter Tool:", c="m", invert=1)
    printc("  Move gray handles to cut away parts of the mesh", c="m")
    printc("  Press X to save file to: clipped.vtk", c="m")
    vp.interactor.Start()

    boxWidget.Off()
    vp.widgets.append(boxWidget)

    vp.interactor.Start()  # allow extra interaction
    return act0

def _addVolumeCutterTool(vol):
    vp = settings.plotter_instance
    if not vp.renderer:
        save_int = vp.interactive
        vp.show(interactive=0)
        vp.interactive = save_int

    boxWidget = vtk.vtkBoxWidget()
    boxWidget.SetInteractor(vp.interactor)
    boxWidget.SetPlaceFactor(1.0)

    vp.cutterWidget = boxWidget

    planes = vtk.vtkPlanes()
    def clipVolumeRender(obj, event):
        obj.GetPlanes(planes)
        vol.mapper().SetClippingPlanes(planes)
        #vol.mapper().Modified()
        #vol.mapper().Update()

    boxWidget.SetInputData(vol.inputdata())
    boxWidget.OutlineCursorWiresOn()
    boxWidget.GetSelectedOutlineProperty().SetColor(1, 0, 1)
    boxWidget.GetOutlineProperty().SetColor(0.2, 0.2, 0.2)
    boxWidget.GetOutlineProperty().SetOpacity(0.7)
    boxWidget.SetPlaceFactor(1.0)
    boxWidget.PlaceWidget()
    boxWidget.InsideOutOn()
    boxWidget.AddObserver("InteractionEvent", clipVolumeRender)

    printc("Volume Cutter Tool:", c="m", invert=1)
    printc("  Move gray handles to cut parts of the volume", c="m")

    vp.renderer.ResetCamera()
    boxWidget.On()

    vp.interactor.Start()
    boxWidget.Off()
    vp.widgets.append(boxWidget)

#####################################################################
def addIcon(iconActor, pos=3, size=0.08):
    """Add an inset icon mesh into the renderer.

    :param pos: icon position in the range [1-4] indicating one of the 4 corners,
                or it can be a tuple (x,y) as a fraction of the renderer size.
    :param float size: size of the icon space.

    |icon| |icon.py|_
    """
    vp = settings.plotter_instance
    if not vp.renderer:
        printc("~lightningWarning: Use addIcon() after first rendering the scene.", c=3)
        save_int = vp.interactive
        vp.show(interactive=0)
        vp.interactive = save_int
    widget = vtk.vtkOrientationMarkerWidget()
    widget.SetOrientationMarker(iconActor)
    widget.SetInteractor(vp.interactor)
    if isSequence(pos):
        widget.SetViewport(pos[0] - size, pos[1] - size, pos[0] + size, pos[1] + size)
    else:
        if pos < 2:
            widget.SetViewport(0, 1 - 2 * size, size * 2, 1)
        elif pos == 2:
            widget.SetViewport(1 - 2 * size, 1 - 2 * size, 1, 1)
        elif pos == 3:
            widget.SetViewport(0, 0, size * 2, size * 2)
        elif pos == 4:
            widget.SetViewport(1 - 2 * size, 0, 1, size * 2)
    widget.EnabledOn()
    widget.InteractiveOff()
    vp.widgets.append(widget)
    if iconActor in vp.actors:
        vp.actors.remove(iconActor)
    return widget

#####################################################################
def computeVisibleBounds():
    """Calculate max actors bounds and sizes."""
    bns = []
    for a in settings.plotter_instance.actors:
        if a and a.GetPickable():
            b = a.GetBounds()
            if b:
                bns.append(b)
    if len(bns):
        max_bns = np.max(bns, axis=0)
        min_bns = np.min(bns, axis=0)
        vbb = (min_bns[0], max_bns[1], min_bns[2], max_bns[3], min_bns[4], max_bns[5])
    else:
        vbb = settings.plotter_instance.renderer.ComputeVisiblePropBounds()
        max_bns = vbb
        min_bns = vbb
    sizes = np.array([max_bns[1]-min_bns[0], max_bns[3]-min_bns[2], max_bns[5]-min_bns[4]])
    return vbb, sizes, min_bns, max_bns

#####################################################################
def addAxes(axtype=None, c=None):
    """Draw axes on scene. Available axes types:

    :param int axtype:

          - 0,  no axes,
          - 1,  draw three gray grid walls
          - 2,  show cartesian axes from (0,0,0)
          - 3,  show positive range of cartesian axes from (0,0,0)
          - 4,  show a triad at bottom left
          - 5,  show a cube at bottom left
          - 6,  mark the corners of the bounding box
          - 7,  draw a simple ruler at the bottom of the window
          - 8,  show the ``vtkCubeAxesActor`` object
          - 9,  show the bounding box outLine
          - 10, show three circles representing the maximum bounding box
          - 11, show a large grid on the x-y plane (use with zoom=8)
          - 12, show polar axes

    Axis type-1 can be fully customized by passing a dictionary ``axes=dict()`` where:

        - `xtitle`,            ['x'], x-axis title text.
        - `ytitle`,            ['y'], y-axis title text.
        - `ztitle`,            ['z'], z-axis title text.
        - `numberOfDivisions`, [automatic], number of divisions on the shortest axis
        - `axesLineWidth`,       [1], width of the axes lines
        - `gridLineWidth`,       [1], width of the grid lines
        - `reorientShortTitle`, [True], titles shorter than 2 letter are placed horizontally
        - `originMarkerSize`, [0.01], draw a small cube on the axis where the origin is
        - `enableLastLabel`, [False], show last numeric label on axes
        - `titleDepth`,          [0], extrusion fractional depth of title text
        - `xyGrid`,           [True], show a gridded wall on plane xy
        - `yzGrid`,           [True], show a gridded wall on plane yz
        - `zxGrid`,           [True], show a gridded wall on plane zx
        - `zxGrid2`,         [False], show zx plane on opposite side of the bounding box
        - `xyGridTransparent`  [False], make grid plane completely transparent
        - `xyGrid2Transparent` [False], make grid plane completely transparent on opposite side box
        - `xyPlaneColor`,   ['gray'], color of the plane
        - `xyGridColor`,    ['gray'], grid line color
        - `xyAlpha`,          [0.15], grid plane opacity
        - `showTicks`,        [True], show major ticks
        - `xTitlePosition`,   [0.32], title fractional positions along axis
        - `xTitleOffset`,     [0.05], title fractional offset distance from axis line
        - `xTitleJustify`, ["top-right"], title justification
        - `xTitleRotation`,      [0], add a rotation of the axis title
        - `xLineColor`,  [automatic], color of the x-axis
        - `xTitleColor`, [automatic], color of the axis title
        - `xTitleBackfaceColor`, [None],  color of axis title on its backface
        - `xTitleSize`,      [0.025], size of the axis title
        - `xHighlightZero`,   [True], draw a line highlighting zero position if in range
        - `xHighlightZeroColor`, [automatic], color of the line highlighting the zero position
        - `xTickRadius`,     [0.005], radius of the major ticks
        - `xTickThickness`, [0.0025], thickness of the major ticks along their axis
        - `xTickColor`,  [automatic], color of major ticks
        - `xMinorTicks`,         [1], number of minor ticks between two major ticks
        - `tipSize`,          [0.01], size of the arrow tip
        - `xLabelPrecision`,     [2], nr. of significative digits to be shown
        - `xLabelSize`,      [0.015], size of the numeric labels along axis
        - `xLabelOffset`,    [0.025], offset of numeric labels

        :Example:

            .. code-block:: python

                from vtkplotter import Box, show
                b = Box(pos=(0,0,0), length=80, width=90, height=70).alpha(0)

                show(b, axes={ 'xtitle':'Some long variable [a.u.]',
                               'numberOfDivisions':4,
                               # ...
                             }
                )

    |customAxes| |customAxes.py|_
    """
    vp = settings.plotter_instance
    if axtype is not None:
        vp.axes = axtype  # override

    r = vp.renderers.index(vp.renderer)

    if not vp.axes:
        return

    if c is None:  # automatic black or white
        c = (0.9, 0.9, 0.9)
        if np.sum(vp.renderer.GetBackground()) > 1.5:
            c = (0.1, 0.1, 0.1)
    else:
        c = getColor(c) # for speed

    if not vp.renderer:
        return

    if vp.axes_instances[r]:
        return

    ############################################################
    if vp.axes == 1 or vp.axes is True or isinstance(vp.axes, dict):  # custom grid walls

        if isinstance(vp.axes, dict):
            axes = vp.axes
            axes_copy = dict(vp.axes)
        else:
            axes = dict() # will cause popping the defaults
            axes_copy = 1

        xtitle = axes.pop('xtitle', vp.xtitle)
        ytitle = axes.pop('ytitle', vp.ytitle)
        ztitle = axes.pop('ztitle', vp.ztitle)

        limitRatio = axes.pop('limitRatio', 20)

        vbb, sizes, min_bns, max_bns = computeVisibleBounds()

        if sizes[0] and (sizes[1]/sizes[0] > limitRatio or sizes[2]/sizes[0] > limitRatio):
            sizes[0] = 0
            xtitle = ''
        if sizes[1] and (sizes[0]/sizes[1] > limitRatio or sizes[2]/sizes[1] > limitRatio):
            sizes[1] = 0
            ytitle = ''
        if sizes[2] and (sizes[0]/sizes[2] > limitRatio or sizes[1]/sizes[2] > limitRatio):
            sizes[2] = 0
            ztitle = ''
        rats = []
        if sizes[0]: rats += [sizes[1]/sizes[0], sizes[2]/sizes[0]]
        if sizes[1]: rats += [sizes[0]/sizes[1], sizes[2]/sizes[1]]
        if sizes[2]: rats += [sizes[0]/sizes[2], sizes[1]/sizes[2]]
        if not len(rats):
            return
        rats = max(rats)
        if rats == 0:
            return

        nrDiv = max(1, int(6.5/rats))

        numberOfDivisions = axes.pop('numberOfDivisions', nrDiv) # number of divisions on the shortest axis

        axesLineWidth = axes.pop('axesLineWidth', 1)
        gridLineWidth = axes.pop('gridLineWidth', 1)
        reorientShortTitle = axes.pop('reorientShortTitle', True)
        originMarkerSize = axes.pop('originMarkerSize', 0)
        enableLastLabel = axes.pop('enableLastLabel', False)
        titleDepth = axes.pop('titleDepth', 0)

        xTitlePosition = axes.pop('xTitlePosition', 0.95) # title fractional positions along axis [0->1]
        yTitlePosition = axes.pop('yTitlePosition', 0.95)
        zTitlePosition = axes.pop('zTitlePosition', 0.95)

        xTitleOffset = axes.pop('xTitleOffset', 0.05)  # title fractional offsets
        yTitleOffset = axes.pop('yTitleOffset', 0.05)  #
        zTitleOffset = axes.pop('zTitleOffset', 0.05)  #

        xTitleJustify = axes.pop('xTitleJustify', "top-right")
        yTitleJustify = axes.pop('yTitleJustify', "bottom-right")
        zTitleJustify = axes.pop('zTitleJustify', "bottom-right")

        xTitleRotation = axes.pop('xTitleRotation', 0)
        yTitleRotation = axes.pop('yTitleRotation', 90)
        zTitleRotation = axes.pop('zTitleRotation', 135)

        xTitleSize = axes.pop('xTitleSize', 0.025)
        yTitleSize = axes.pop('yTitleSize', 0.025)
        zTitleSize = axes.pop('zTitleSize', 0.025)

        xTitleColor = axes.pop('xTitleColor', c)
        yTitleColor = axes.pop('yTitleColor', c)
        zTitleColor = axes.pop('zTitleColor', c)

        xTitleBackfaceColor = axes.pop('xTitleBackfaceColor', None)
        yTitleBackfaceColor = axes.pop('yTitleBackfaceColor', None)
        zTitleBackfaceColor = axes.pop('zTitleBackfaceColor', None)

        xKeepAspectRatio = axes.pop('xKeepAspectRatio', True)
        yKeepAspectRatio = axes.pop('yKeepAspectRatio', True)
        zKeepAspectRatio = axes.pop('zKeepAspectRatio', True)

        xyGrid = axes.pop('xyGrid', True)
        yzGrid = axes.pop('yzGrid', True)
        zxGrid = axes.pop('zxGrid', False)
        xyGrid2 = axes.pop('xyGrid2', False) # opposite side grid
        yzGrid2 = axes.pop('yzGrid2', False)
        zxGrid2 = axes.pop('zxGrid2', False)
        if settings.plotter_instance and settings.plotter_instance.renderer.GetUseDepthPeeling():
            xyGrid = False
            yzGrid = False
            zxGrid = False
            xyGrid2 = False
            yzGrid2 = False
            zxGrid2 = False

        xyGridTransparent = axes.pop('xyGridTransparent', False)
        yzGridTransparent = axes.pop('yzGridTransparent', False)
        zxGridTransparent = axes.pop('zxGridTransparent', False)
        xyGrid2Transparent = axes.pop('xyGrid2Transparent', False)
        yzGrid2Transparent = axes.pop('yzGrid2Transparent', False)
        zxGrid2Transparent = axes.pop('zxGrid2Transparent', False)

        xyPlaneColor = axes.pop('xyPlaneColor', c)
        yzPlaneColor = axes.pop('yzPlaneColor', c)
        zxPlaneColor = axes.pop('zxPlaneColor', c)
        xyGridColor = axes.pop('xyGridColor', c)
        yzGridColor = axes.pop('yzGridColor', c)
        zxGridColor = axes.pop('zxGridColor', c)
        xyAlpha = axes.pop('xyAlpha', 0.05)
        yzAlpha = axes.pop('yzAlpha', 0.05)
        zxAlpha = axes.pop('zxAlpha', 0.05)

        xLineColor = axes.pop('xLineColor', c)
        yLineColor = axes.pop('yLineColor', c)
        zLineColor = axes.pop('zLineColor', c)

        xHighlightZero = axes.pop('xHighlightZero', False)
        yHighlightZero = axes.pop('yHighlightZero', False)
        zHighlightZero = axes.pop('zHighlightZero', False)
        xHighlightZeroColor = axes.pop('xHighlightZeroColor', 'red')
        yHighlightZeroColor = axes.pop('yHighlightZeroColor', 'green')
        zHighlightZeroColor = axes.pop('zHighlightZeroColor', 'blue')

        showTicks = axes.pop('showTicks', True)
        xTickRadius = axes.pop('xTickRadius', 0.005)
        yTickRadius = axes.pop('yTickRadius', 0.005)
        zTickRadius = axes.pop('zTickRadius', 0.005)

        xTickThickness = axes.pop('xTickThickness', 0.0025)
        yTickThickness = axes.pop('yTickThickness', 0.0025)
        zTickThickness = axes.pop('zTickThickness', 0.0025)

        xTickColor = axes.pop('xTickColor', xLineColor)
        yTickColor = axes.pop('yTickColor', yLineColor)
        zTickColor = axes.pop('zTickColor', zLineColor)

        xMinorTicks = axes.pop('xMinorTicks', 1)
        yMinorTicks = axes.pop('yMinorTicks', 1)
        zMinorTicks = axes.pop('zMinorTicks', 1)

        tipSize = axes.pop('tipSize', 0.01)

        xLabelPrecision = axes.pop('xLabelPrecision', 2) # nr. of significant digits
        yLabelPrecision = axes.pop('yLabelPrecision', 2)
        zLabelPrecision = axes.pop('zLabelPrecision', 2)

        xLabelSize = axes.pop('xLabelSize', 0.0175)
        yLabelSize = axes.pop('yLabelSize', 0.0175)
        zLabelSize = axes.pop('zLabelSize', 0.0175)

        xLabelOffset = axes.pop('xLabelOffset', 0.015)
        yLabelOffset = axes.pop('yLabelOffset', 0.015)
        zLabelOffset = axes.pop('zLabelOffset', 0.01)

        ########################
        step = np.min(sizes[np.nonzero(sizes)]) / numberOfDivisions
        rx, ry, rz = np.rint(sizes / step).astype(int)
        if rx==0: xtitle=''
        if ry==0: ytitle=''
        if rz==0: ztitle=''

        if enableLastLabel:
            enableLastLabel = 1
        else:
            enableLastLabel = 0

        ################################################ axes lines
        lines = []
        if xtitle: lines.append(shapes.Line([0, 0, 0], [1, 0, 0], c=xLineColor, lw=axesLineWidth))
        if ytitle: lines.append(shapes.Line([0, 0, 0], [0, 1, 0], c=yLineColor, lw=axesLineWidth))
        if ztitle: lines.append(shapes.Line([0, 0, 0], [0, 0, 1], c=zLineColor, lw=axesLineWidth))

        ################################################ grid planes
        grids = []
        if xyGrid and xtitle and ytitle:
            gxy = shapes.Grid(pos=(0.5, 0.5, 0), normal=[0, 0, 1], resx=rx, resy=ry)
            gxy.alpha(xyAlpha).wireframe(xyGridTransparent).c(xyPlaneColor).lw(gridLineWidth).lc(xyGridColor)
            grids.append(gxy)
        if yzGrid and ytitle and ztitle:
            gyz = shapes.Grid(pos=(0, 0.5, 0.5), normal=[1, 0, 0], resx=rz, resy=ry)
            gyz.alpha(yzAlpha).wireframe(yzGridTransparent).c(yzPlaneColor).lw(gridLineWidth).lc(yzGridColor)
            grids.append(gyz)
        if zxGrid and ztitle and xtitle:
            gzx = shapes.Grid(pos=(0.5, 0, 0.5), normal=[0, 1, 0], resx=rz, resy=rx)
            gzx.alpha(zxAlpha).wireframe(zxGridTransparent).c(zxPlaneColor).lw(gridLineWidth).lc(zxGridColor)
            grids.append(gzx)

        grids2 = []
        if xyGrid2 and xtitle and ytitle:
            gxy2 = shapes.Grid(pos=(0.5, 0.5, 1), normal=[0, 0, 1], resx=rx, resy=ry)
            gxy2.alpha(xyAlpha).wireframe(xyGrid2Transparent).c(xyPlaneColor).lw(gridLineWidth).lc(xyGridColor)
            grids2.append(gxy2)
        if yzGrid2 and ytitle and ztitle:
            gyz2 = shapes.Grid(pos=(1, 0.5, 0.5), normal=[1, 0, 0], resx=rz, resy=ry)
            gyz2.alpha(yzAlpha).wireframe(yzGrid2Transparent).c(yzPlaneColor).lw(gridLineWidth).lc(yzGridColor)
            grids2.append(gyz2)
        if zxGrid2 and ztitle and xtitle:
            gzx2 = shapes.Grid(pos=(0.5, 1, 0.5), normal=[0, 1, 0], resx=rz, resy=rx)
            gzx2.alpha(zxAlpha).wireframe(zxGrid2Transparent).c(zxPlaneColor).lw(gridLineWidth).lc(zxGridColor)
            grids2.append(gzx2)


        ################################################ zero lines highlights
        highlights = []
        if xyGrid and xtitle and ytitle:
            if xHighlightZero and min_bns[0] <= 0 and max_bns[1] > 0:
                xhl = -min_bns[0] / sizes[0]
                hxy = shapes.Line([xhl,0,0], [xhl,1,0], c=xHighlightZeroColor)
                hxy.alpha(np.sqrt(xyAlpha)).lw(gridLineWidth*2)
                highlights.append(hxy)
            if yHighlightZero and min_bns[2] <= 0 and max_bns[3] > 0:
                yhl = -min_bns[2] / sizes[1]
                hyx = shapes.Line([0,yhl,0], [1,yhl,0], c=yHighlightZeroColor)
                hyx.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
                highlights.append(hyx)

        if yzGrid and ytitle and ztitle:
            if yHighlightZero and min_bns[2] <= 0 and max_bns[3] > 0:
                yhl = -min_bns[2] / sizes[1]
                hyz = shapes.Line([0,yhl,0], [0,yhl,1], c=yHighlightZeroColor)
                hyz.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
                highlights.append(hyz)
            if zHighlightZero and min_bns[4] <= 0 and max_bns[5] > 0:
                zhl = -min_bns[4] / sizes[2]
                hzy = shapes.Line([0,0,zhl], [0,1,zhl], c=zHighlightZeroColor)
                hzy.alpha(np.sqrt(yzAlpha)).lw(gridLineWidth*2)
                highlights.append(hzy)

        if zxGrid and ztitle and xtitle:
            if zHighlightZero and min_bns[4] <= 0 and max_bns[5] > 0:
                zhl = -min_bns[4] / sizes[2]
                hzx = shapes.Line([0,0,zhl], [1,0,zhl], c=zHighlightZeroColor)
                hzx.alpha(np.sqrt(zxAlpha)).lw(gridLineWidth*2)
                highlights.append(hzx)
            if xHighlightZero and min_bns[0] <= 0 and max_bns[1] > 0:
                xhl = -min_bns[0] / sizes[0]
                hxz = shapes.Line([xhl,0,0], [xhl,0,1], c=xHighlightZeroColor)
                hxz.alpha(np.sqrt(zxAlpha)).lw(gridLineWidth*2)
                highlights.append(hxz)

        ################################################ aspect ratio scales
        x_aspect_ratio_scale=1
        y_aspect_ratio_scale=1
        z_aspect_ratio_scale=1
        if xtitle:
            if sizes[0] > sizes[1]:
                x_aspect_ratio_scale = (1, sizes[0]/sizes[1], 1)
            else:
                x_aspect_ratio_scale = (sizes[1]/sizes[0], 1, 1)

        if ytitle:
            if sizes[0] > sizes[1]:
                y_aspect_ratio_scale = (sizes[0]/sizes[1], 1, 1)
            else:
                y_aspect_ratio_scale = (1, sizes[1]/sizes[0], 1)

        if ztitle:
            smean = (sizes[0]+sizes[1])/2
            if smean:
                if sizes[2] > smean:
                    zarfact = smean/sizes[2]
                    z_aspect_ratio_scale = (zarfact, zarfact*sizes[2]/smean, zarfact)
                else:
                    z_aspect_ratio_scale = (smean/sizes[2], 1, 1)

        ################################################ axes titles
        titles = []
        if xtitle:
            xt = shapes.Text(xtitle, pos=(0,0,0), s=xTitleSize, bc=xTitleBackfaceColor,
                             c=xTitleColor, justify=xTitleJustify, depth=titleDepth)
            if reorientShortTitle and len(ytitle) < 3:  # title is short
                wpos = [xTitlePosition, -xTitleOffset +0.02, 0]
            else:
                wpos = [xTitlePosition, -xTitleOffset, 0]
            if xKeepAspectRatio: xt.SetScale(x_aspect_ratio_scale)
            xt.RotateX(xTitleRotation)
            xt.pos(wpos)
            titles.append(xt.lighting(specular=0, diffuse=0, ambient=1))

        if ytitle:
            yt = shapes.Text(ytitle, pos=(0, 0, 0), s=yTitleSize, bc=yTitleBackfaceColor,
                             c=yTitleColor, justify=yTitleJustify, depth=titleDepth)
            if reorientShortTitle and len(ytitle) < 3:  # title is short
                wpos = [-yTitleOffset +0.03-0.01*len(ytitle), yTitlePosition, 0]
                if yKeepAspectRatio: yt.SetScale(x_aspect_ratio_scale) #x!
            else:
                wpos = [-yTitleOffset, yTitlePosition, 0]
                if yKeepAspectRatio: yt.SetScale(y_aspect_ratio_scale)
                yt.RotateZ(yTitleRotation)
            yt.pos(wpos)
            titles.append(yt.lighting(specular=0, diffuse=0, ambient=1))

        if ztitle:
            zt = shapes.Text(ztitle, pos=(0, 0, 0), s=zTitleSize, bc=zTitleBackfaceColor,
                             c=zTitleColor, justify=zTitleJustify, depth=titleDepth)
            if reorientShortTitle and len(ztitle) < 3:  # title is short
                wpos = [(-zTitleOffset+0.02-0.003*len(ztitle))/1.42,
                        (-zTitleOffset+0.02-0.003*len(ztitle))/1.42, zTitlePosition]
                if zKeepAspectRatio:
                    zr2 = (z_aspect_ratio_scale[1], z_aspect_ratio_scale[0], z_aspect_ratio_scale[2])
                    zt.SetScale(zr2)
                zt.RotateX(90)
                zt.RotateY(45)
                zt.pos(wpos)
            else:
                if zKeepAspectRatio: zt.SetScale(z_aspect_ratio_scale)
                wpos = [-zTitleOffset/1.42, -zTitleOffset/1.42, zTitlePosition]
                zt.RotateY(-90)
                zt.RotateX(zTitleRotation)
                zt.pos(wpos)
            titles.append(zt.lighting(specular=0, diffuse=0, ambient=1))

        ################################################ cube origin ticks
        originmarks = []
        if originMarkerSize:
            if xtitle:
                if min_bns[0] <= 0 and max_bns[1] > 0:  # mark x origin
                    ox = shapes.Cube([-min_bns[0] / sizes[0], 0, 0], side=originMarkerSize, c=xLineColor)
                    originmarks.append(ox.lighting(specular=0, diffuse=0, ambient=1))

            if ytitle:
                if min_bns[2] <= 0 and max_bns[3] > 0:  # mark y origin
                    oy = shapes.Cube([0, -min_bns[2] / sizes[1], 0], side=originMarkerSize, c=yLineColor)
                    originmarks.append(oy.lighting(specular=0, diffuse=0, ambient=1))

            if ztitle:
                if min_bns[4] <= 0 and max_bns[5] > 0:  # mark z origin
                    oz = shapes.Cube([0, 0, -min_bns[4] / sizes[2]], side=originMarkerSize, c=zLineColor)
                    originmarks.append(oz.lighting(specular=0, diffuse=0, ambient=1))

        ################################################ arrow cone
        cones = []
        if tipSize:
            if xtitle:
                cx = shapes.Cone((1,0,0), r=tipSize, height=tipSize*2, axis=(1,0,0), c=xLineColor, res=10)
                cones.append(cx.lighting(specular=0, diffuse=0, ambient=1))
            if ytitle:
                cy = shapes.Cone((0,1,0), r=tipSize, height=tipSize*2, axis=(0,1,0), c=yLineColor, res=10)
                cones.append(cy.lighting(specular=0, diffuse=0, ambient=1))
            if ztitle:
                cz = shapes.Cone((0,0,1), r=tipSize, height=tipSize*2, axis=(0,0,1), c=zLineColor, res=10)
                cones.append(cz.lighting(specular=0, diffuse=0, ambient=1))

        ################################################ cylindrical ticks
        ticks = []
        if showTicks:
            if xtitle:
                for coo in range(1 ,rx):
                    v = [coo/rx,0,0]
                    xds = shapes.Cylinder(v, r=xTickRadius, height=xTickThickness, axis=(1,0,0), res=10)
                    ticks.append(xds.c(xTickColor).lighting(specular=0, ambient=1))
            if ytitle:
                for coo in range(1 ,ry):
                    v = [0,coo/ry,0]
                    yds = shapes.Cylinder(v, r=yTickRadius, height=yTickThickness, axis=(0,1,0), res=10)
                    ticks.append(yds.c(yTickColor).lighting(specular=0, ambient=1))
            if ztitle:
                for coo in range(1 ,rz):
                    v = [0,0,coo/rz]
                    zds = shapes.Cylinder(v, r=zTickRadius, height=zTickThickness, axis=(0,0,1), res=10)
                    ticks.append(zds.c(zTickColor).lighting(specular=0, ambient=1))

        ################################################ MINOR cylindrical ticks
        minorticks = []
        if xMinorTicks and xtitle:
            xMinorTicks += 1
            for coo in range(1, rx*xMinorTicks):
                v = [coo/rx/xMinorTicks,0,0]
                mxds = shapes.Cylinder(v, r=xTickRadius/1.5, height=xTickThickness, axis=(1,0,0), res=6)
                minorticks.append(mxds.c(xTickColor).lighting(specular=0, ambient=1))
        if yMinorTicks and ytitle:
            yMinorTicks += 1
            for coo in range(1, ry*yMinorTicks):
                v = [0, coo/ry/yMinorTicks,0]
                myds = shapes.Cylinder(v, r=yTickRadius/1.5, height=yTickThickness, axis=(0,1,0), res=6)
                minorticks.append(myds.c(yTickColor).lighting(specular=0, ambient=1))
        if zMinorTicks and ztitle:
            zMinorTicks += 1
            for coo in range(1, rz*zMinorTicks):
                v = [0, 0, coo/rz/zMinorTicks]
                mzds = shapes.Cylinder(v, r=zTickRadius/1.5, height=zTickThickness, axis=(0,0,1), res=6)
                minorticks.append(mzds.c(zTickColor).lighting(specular=0, ambient=1))

        ################################################ axes tick NUMERIC labels
        labels = []
        if xLabelSize:
            if xtitle:
                if rx > 12: rx = int(rx/2)
                for ic in range(1, rx+enableLastLabel):
                    v = (ic/rx, -xLabelOffset, 0)
                    val = v[0]*sizes[0]+min_bns[0]
                    if abs(val)>1 and sizes[0]<1: xLabelPrecision = int(xLabelPrecision-np.log10(sizes[0]))
                    tval = precision(val, xLabelPrecision, vrange=sizes[0])
                    xlab = shapes.Text(tval, pos=v, s=xLabelSize, justify="center-top", depth=0)
                    if xKeepAspectRatio: xlab.SetScale(x_aspect_ratio_scale)
                    labels.append(xlab.c(xTickColor).lighting(specular=0, ambient=1))
        if yLabelSize:
            if ytitle:
                if ry > 12: ry = int(ry/2)
                for ic in range(1, ry+enableLastLabel):
                    v = (-yLabelOffset, ic/ry, 0)
                    val = v[1]*sizes[1]+min_bns[2]
                    if abs(val)>1 and sizes[1]<1: yLabelPrecision = int(yLabelPrecision-np.log10(sizes[1]))
                    tval = precision(val, yLabelPrecision, vrange=sizes[1])
                    ylab = shapes.Text(tval, pos=(0,0,0), s=yLabelSize, justify="center-bottom", depth=0)
                    if yKeepAspectRatio: ylab.SetScale(y_aspect_ratio_scale)
                    ylab.RotateZ(yTitleRotation)
                    ylab.pos(v)
                    labels.append(ylab.c(yTickColor).lighting(specular=0, ambient=1))
        if zLabelSize:
            if ztitle:
                if rz > 12: rz = int(rz/2)
                for ic in range(1, rz+enableLastLabel):
                    v = (-zLabelOffset, -zLabelOffset, ic/rz)
                    val = v[2]*sizes[2]+min_bns[4]
                    tval = precision(val, zLabelPrecision, vrange=sizes[2])
                    if abs(val)>1 and sizes[2]<1: zLabelPrecision = int(zLabelPrecision-np.log10(sizes[2]))
                    zlab = shapes.Text(tval, pos=(0,0,0), s=zLabelSize, justify="center-bottom", depth=0)
                    if zKeepAspectRatio: zlab.SetScale(z_aspect_ratio_scale)
                    zlab.RotateY(-90)
                    zlab.RotateX(zTitleRotation)
                    zlab.pos(v)
                    labels.append(zlab.c(zTickColor).lighting(specular=0, ambient=1))

        acts = grids + grids2 + lines + highlights + titles
        acts += minorticks + originmarks + ticks + cones + labels
        for a in acts:
            a.PickableOff()
        asse = Assembly(acts)
        asse.pos(min_bns[0], min_bns[2], min_bns[4])
        asse.SetScale(sizes)
        asse.PickableOff()
        vp.renderer.AddActor(asse)
        vp.axes_instances[r] = asse
        vp.axes = axes_copy #un-pop


    elif vp.axes == 2 or vp.axes == 3:
        x0, x1, y0, y1, z0, z1 = vp.renderer.ComputeVisiblePropBounds()
        xcol, ycol, zcol = "dr", "dg", "db"
        s = 1
        alpha = 1
        centered = False
        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
        aves = np.sqrt(dx * dx + dy * dy + dz * dz) / 2
        x0, x1 = min(x0, 0), max(x1, 0)
        y0, y1 = min(y0, 0), max(y1, 0)
        z0, z1 = min(z0, 0), max(z1, 0)

        if vp.axes == 3:
            if x1 > 0:
                x0 = 0
            if y1 > 0:
                y0 = 0
            if z1 > 0:
                z0 = 0

        dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
        acts = []
        if x0 * x1 <= 0 or y0 * z1 <= 0 or z0 * z1 <= 0:  # some ranges contain origin
            zero = shapes.Sphere(r=aves / 120 * s, c="k", alpha=alpha, res=10)
            acts += [zero]

        if len(vp.xtitle) and dx > aves/100:
            xl = shapes.Cylinder([[x0, 0, 0], [x1, 0, 0]], r=aves/250*s, c=xcol, alpha=alpha)
            xc = shapes.Cone(pos=[x1, 0, 0], c=xcol, alpha=alpha,
                             r=aves/100*s, height=aves/25*s, axis=[1, 0, 0], res=10)
            wpos = [x1-(len(vp.xtitle)+1)*aves/40*s, -aves/25*s, 0]  # aligned to arrow tip
            if centered:
                wpos = [(x0 + x1) / 2 - len(vp.xtitle) / 2 * aves / 40 * s, -aves / 25 * s, 0]
            xt = shapes.Text(vp.xtitle, pos=wpos, s=aves / 40 * s, c=xcol)
            acts += [xl, xc, xt]

        if len(vp.ytitle) and dy > aves/100:
            yl = shapes.Cylinder([[0, y0, 0], [0, y1, 0]], r=aves/250*s, c=ycol, alpha=alpha)
            yc = shapes.Cone(pos=[0, y1, 0], c=ycol, alpha=alpha,
                             r=aves/100*s, height=aves/25*s, axis=[0, 1, 0], res=10)
            wpos = [-aves/40*s, y1-(len(vp.ytitle)+1)*aves/40*s, 0]
            if centered:
                wpos = [-aves / 40 * s, (y0 + y1) / 2 - len(vp.ytitle) / 2 * aves / 40 * s, 0]
            yt = shapes.Text(vp.ytitle, pos=(0, 0, 0), s=aves / 40 * s, c=ycol)
            yt.rotate(90, [0, 0, 1]).pos(wpos)
            acts += [yl, yc, yt]

        if len(vp.ztitle) and dz > aves/100:
            zl = shapes.Cylinder([[0, 0, z0], [0, 0, z1]], r=aves/250*s, c=zcol, alpha=alpha)
            zc = shapes.Cone(pos=[0, 0, z1], c=zcol, alpha=alpha,
                             r=aves/100*s, height=aves/25*s, axis=[0, 0, 1], res=10)
            wpos = [-aves/50*s, -aves/50*s, z1 - (len(vp.ztitle)+1)*aves/40*s]
            if centered:
                wpos = [-aves/50*s, -aves/50*s, (z0+z1)/2-len(vp.ztitle)/2*aves/40*s]
            zt = shapes.Text(vp.ztitle, pos=(0,0,0), s=aves/40*s, c=zcol)
            zt.rotate(180, (1, -1, 0)).pos(wpos)
            acts += [zl, zc, zt]
        for a in acts:
            a.PickableOff()
        ass = Assembly(acts)
        ass.PickableOff()
        vp.renderer.AddActor(ass)
        vp.axes_instances[r] = ass

    elif vp.axes == 4:
        axact = vtk.vtkAxesActor()
        axact.SetShaftTypeToCylinder()
        axact.SetCylinderRadius(0.03)
        axact.SetXAxisLabelText(vp.xtitle)
        axact.SetYAxisLabelText(vp.ytitle)
        axact.SetZAxisLabelText(vp.ztitle)
        axact.GetXAxisShaftProperty().SetColor(1, 0, 0)
        axact.GetYAxisShaftProperty().SetColor(0, 1, 0)
        axact.GetZAxisShaftProperty().SetColor(0, 0, 1)
        axact.GetXAxisTipProperty().SetColor(1, 0, 0)
        axact.GetYAxisTipProperty().SetColor(0, 1, 0)
        axact.GetZAxisTipProperty().SetColor(0, 0, 1)
        bc = np.array(vp.renderer.GetBackground())
        if np.sum(bc) < 1.5:
            lc = (1, 1, 1)
        else:
            lc = (0, 0, 0)
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().BoldOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ItalicOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().ShadowOff()
        axact.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(lc)
        axact.PickableOff()
        icn = addIcon(axact, size=0.1)
        vp.axes_instances[r] = icn

    elif vp.axes == 5:
        axact = vtk.vtkAnnotatedCubeActor()
        axact.GetCubeProperty().SetColor(0.75, 0.75, 0.75)
        axact.SetTextEdgesVisibility(0)
        axact.SetFaceTextScale(0.2)
        axact.SetXPlusFaceText ( "right" )
        axact.SetXMinusFaceText( "left " )
        axact.SetYPlusFaceText ( "front" )
        axact.SetYMinusFaceText( "back " )
        axact.SetZPlusFaceText ( " top " )
        axact.SetZMinusFaceText( "bttom" )
        axact.SetZFaceTextRotation(90)

        axact.GetXPlusFaceProperty().SetColor(getColor("r"))
        axact.GetXMinusFaceProperty().SetColor(getColor("dr"))
        axact.GetYPlusFaceProperty().SetColor(getColor("g"))
        axact.GetYMinusFaceProperty().SetColor(getColor("dg"))
        axact.GetZPlusFaceProperty().SetColor(getColor("b"))
        axact.GetZMinusFaceProperty().SetColor(getColor("db"))
        axact.PickableOff()
        icn = addIcon(axact, size=0.06)
        vp.axes_instances[r] = icn

    elif vp.axes == 6:
        ocf = vtk.vtkOutlineCornerFilter()
        ocf.SetCornerFactor(0.1)
        largestact, sz = None, -1
        for a in vp.actors:
            if a.GetPickable():
                b = a.GetBounds()
                if b is None:
                    return
                d = max(b[1]-b[0], b[3]-b[2], b[5]-b[4])
                if sz < d:
                    largestact = a
                    sz = d
        if isinstance(largestact, Assembly):
            ocf.SetInputData(largestact.getActor(0).GetMapper().GetInput())
        else:
            ocf.SetInputData(largestact.GetMapper().GetInput())
        ocf.Update()
        ocMapper = vtk.vtkHierarchicalPolyDataMapper()
        ocMapper.SetInputConnection(0, ocf.GetOutputPort(0))
        ocActor = vtk.vtkActor()
        ocActor.SetMapper(ocMapper)
        bc = np.array(vp.renderer.GetBackground())
        if np.sum(bc) < 1.5:
            lc = (1, 1, 1)
        else:
            lc = (0, 0, 0)
        ocActor.GetProperty().SetColor(lc)
        ocActor.PickableOff()
        vp.renderer.AddActor(ocActor)
        vp.axes_instances[r] = ocActor

    elif vp.axes == 7:
        # draws a simple ruler at the bottom of the window
        ls = vtk.vtkLegendScaleActor()
        ls.RightAxisVisibilityOff()
        ls.TopAxisVisibilityOff()
        ls.LegendVisibilityOff()
        ls.LeftAxisVisibilityOff()
        ls.GetBottomAxis().SetNumberOfMinorTicks(1)
        ls.GetBottomAxis().GetProperty().SetColor(c)
        ls.GetBottomAxis().GetLabelTextProperty().SetColor(c)
        ls.GetBottomAxis().GetLabelTextProperty().BoldOff()
        ls.GetBottomAxis().GetLabelTextProperty().ItalicOff()
        ls.GetBottomAxis().GetLabelTextProperty().ShadowOff()
        ls.PickableOff()
        vp.renderer.AddActor(ls)
        vp.axes_instances[r] = ls

    elif vp.axes == 8:
        vbb = computeVisibleBounds()[0]
        ca = vtk.vtkCubeAxesActor()
        ca.SetBounds(vbb)
        ca.SetCamera(vp.renderer.GetActiveCamera())
        ca.GetXAxesLinesProperty().SetColor(c)
        ca.GetYAxesLinesProperty().SetColor(c)
        ca.GetZAxesLinesProperty().SetColor(c)
        for i in range(3):
            ca.GetLabelTextProperty(i).SetColor(c)
            ca.GetTitleTextProperty(i).SetColor(c)
        ca.SetTitleOffset(5)
        ca.SetFlyMode(3)
        ca.SetXTitle(vp.xtitle)
        ca.SetYTitle(vp.ytitle)
        ca.SetZTitle(vp.ztitle)
        if vp.xtitle == "":
            ca.SetXAxisVisibility(0)
            ca.XAxisLabelVisibilityOff()
        if vp.ytitle == "":
            ca.SetYAxisVisibility(0)
            ca.YAxisLabelVisibilityOff()
        if vp.ztitle == "":
            ca.SetZAxisVisibility(0)
            ca.ZAxisLabelVisibilityOff()
        ca.PickableOff()
        vp.renderer.AddActor(ca)
        vp.axes_instances[r] = ca

    elif vp.axes == 9:
        vbb = computeVisibleBounds()[0]
        src = vtk.vtkCubeSource()
        src.SetXLength(vbb[1] - vbb[0])
        src.SetYLength(vbb[3] - vbb[2])
        src.SetZLength(vbb[5] - vbb[4])
        src.Update()
        ca = Actor(src.GetOutput(), c, 0.5).wireframe(True)
        ca.pos((vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2)
        ca.PickableOff()
        vp.renderer.AddActor(ca)
        vp.axes_instances[r] = ca

    elif vp.axes == 10:
        vbb = computeVisibleBounds()[0]
        x0 = (vbb[0] + vbb[1]) / 2, (vbb[3] + vbb[2]) / 2, (vbb[5] + vbb[4]) / 2
        rx, ry, rz = (vbb[1]-vbb[0])/2, (vbb[3]-vbb[2])/2, (vbb[5]-vbb[4])/2
        rm = max(rx, ry, rz)
        xc = shapes.Disc(x0, r1=rm, r2=rm, c='lr', res=1, resphi=72)
        yc = shapes.Disc(x0, r1=rm, r2=rm, c='lg', res=1, resphi=72)
        yc.RotateX(90)
        zc = shapes.Disc(x0, r1=rm, r2=rm, c='lb', res=1, resphi=72)
        yc.RotateY(90)
        xc.clean().alpha(0.2).wireframe().lineWidth(2.5).PickableOff()
        yc.clean().alpha(0.2).wireframe().lineWidth(2.5).PickableOff()
        zc.clean().alpha(0.2).wireframe().lineWidth(2.5).PickableOff()
        ca = xc + yc + zc
        ca.PickableOff()
        vp.renderer.AddActor(ca)
        vp.axes_instances[r] = ca

    elif vp.axes == 11:
        vbb, sizes = computeVisibleBounds()[0:2]
        xpos, ypos = (vbb[1] + vbb[0]) /2, (vbb[3] + vbb[2]) /2
        gr = shapes.Grid((xpos, ypos, vbb[4]),
                         sx=sizes[0]*8, sy=sizes[1]*8,
                         resx=7, resy=7,
                         c=c, alpha=0.2)
        gr.lighting('ambient').PickableOff()
        vp.renderer.AddActor(gr)
        vp.axes_instances[r] = gr

    elif vp.axes == 12:
        polaxes = vtk.vtkPolarAxesActor()
        vbb = computeVisibleBounds()[0]

        if vp.xtitle == 'x':
            polaxes.SetPolarAxisTitle('radial distance')
        else:
            polaxes.SetPolarAxisTitle(vp.xtitle)
        polaxes.SetPole(0,0, vbb[4])
        rd = max(abs(vbb[0]), abs(vbb[2]), abs(vbb[1]), abs(vbb[3]))
        polaxes.SetMaximumRadius(rd)
        polaxes.AutoSubdividePolarAxisOff()
        polaxes.SetNumberOfPolarAxisTicks(10)
        polaxes.SetCamera(vp.renderer.GetActiveCamera())
        polaxes.SetPolarLabelFormat("%6.1f")
        polaxes.PolarLabelVisibilityOff() # due to bad overlap of labels

        polaxes.GetPolarArcsProperty().SetColor(c)
        polaxes.GetPolarAxisProperty().SetColor(c)
        polaxes.GetPolarAxisTitleTextProperty().SetColor(c)
        polaxes.GetPolarAxisLabelTextProperty().SetColor(c)
        polaxes.GetLastRadialAxisTextProperty().SetColor(c)
        polaxes.GetSecondaryRadialAxesTextProperty().SetColor(c)
        polaxes.GetSecondaryRadialAxesProperty().SetColor(c)
        polaxes.GetSecondaryPolarArcsProperty().SetColor(c)

        polaxes.SetMinimumAngle(0.)
        polaxes.SetMaximumAngle(315.)
        polaxes.SetNumberOfPolarAxisTicks(5)
        polaxes.PickableOff()
        vp.renderer.AddActor(polaxes)
        vp.axes_instances[r] = polaxes

    else:
        printc('~bomb Keyword axes must be in range [0-10].', c=1)
        printc('''
  ~target Available axes types:
  0 = no axes,
  1 = draw three customizable gray grid walls
  2 = show cartesian axes from (0,0,0)
  3 = show positive range of cartesian axes from (0,0,0)
  4 = show a triad at bottom left
  5 = show a cube at bottom left
  6 = mark the corners of the bounding box
  7 = draw a simple ruler at the bottom of the window
  8 = show the vtkCubeAxesActor object
  9 = show the bounding box outline
  10 = show three circles representing the maximum bounding box
  11 = show a large grid on the x-y plane (use with zoom=8)
  12 = show polar axes.
  ''', c=1, bold=0)

    if not vp.axes_instances[r]:
        vp.axes_instances[r] = True
    return


#####################################################################
def addRendererFrame(c=None, alpha=0.5, bg=None, lw=0.5):

    if c is None:  # automatic black or white
        c = (0.9, 0.9, 0.9)
        if np.sum(settings.plotter_instance.renderer.GetBackground())>1.5:
            c = (0.1, 0.1, 0.1)
    c = getColor(c)

    ppoints = vtk.vtkPoints()  # Generate the polyline
    psqr = [[0,0],[0,1],[1,1],[1,0],[0,0]]
    for i, pt in enumerate(psqr):
            ppoints.InsertPoint(i, pt[0], pt[1], 0)
    lines = vtk.vtkCellArray()
    lines.InsertNextCell(len(psqr))
    for i in range(len(psqr)):
        lines.InsertCellPoint(i)
    pd = vtk.vtkPolyData()
    pd.SetPoints(ppoints)
    pd.SetLines(lines)

    mapper = vtk.vtkPolyDataMapper2D()
    mapper.SetInputData(pd)
    cs = vtk.vtkCoordinate()
    cs.SetCoordinateSystemToNormalizedViewport()
    mapper.SetTransformCoordinate(cs)

    fractor = vtk.vtkActor2D()
    fractor.GetPositionCoordinate().SetValue(0, 0)
    fractor.GetPosition2Coordinate().SetValue(1, 1)
    fractor.SetMapper(mapper)
    fractor.GetProperty().SetColor(c)
    fractor.GetProperty().SetOpacity(alpha)
    fractor.GetProperty().SetLineWidth(lw)

    settings.plotter_instance.renderer.AddActor(fractor)


def addLegend():

    vp = settings.plotter_instance
    if not isSequence(vp._legend):
        return

    # remove old legend if present on current renderer:
    acs = vp.renderer.GetActors2D()
    acs.InitTraversal()
    for i in range(acs.GetNumberOfItems()):
        a = acs.GetNextItem()
        if isinstance(a, vtk.vtkLegendBoxActor):
            vp.renderer.RemoveActor(a)

    actors = vp.getActors()
    acts, texts = [], []
    for i, a in enumerate(actors):
        if i < len(vp._legend) and vp._legend[i] != "":
            if isinstance(vp._legend[i], str):
                texts.append(vp._legend[i])
                acts.append(a)
        elif hasattr(a, "_legend") and a._legend:
            if isinstance(a._legend, str):
                texts.append(a._legend)
                acts.append(a)

    NT = len(texts)
    if NT > 20:
        NT = 20
    vtklegend = vtk.vtkLegendBoxActor()
    vtklegend.SetNumberOfEntries(NT)
    vtklegend.ScalarVisibilityOff()
    for i in range(NT):
        ti = texts[i]
        if not ti:
            continue
        a = acts[i]
        c = a.GetProperty().GetColor()
        if c == (1, 1, 1):
            c = (0.2, 0.2, 0.2)
        vtklegend.SetEntry(i, a.polydata(), "  " + ti, c)
    pos = vp.legendPos
    width = vp.legendSize
    vtklegend.SetWidth(width)
    vtklegend.SetHeight(width / 5.0 * NT)
    sx, sy = 1 - width, 1 - width / 5.0 * NT
    if pos == 1:
        vtklegend.GetPositionCoordinate().SetValue(0, sy)
    elif pos == 2:
        vtklegend.GetPositionCoordinate().SetValue(sx, sy)  # default
    elif pos == 3:
        vtklegend.GetPositionCoordinate().SetValue(0, 0)
    elif pos == 4:
        vtklegend.GetPositionCoordinate().SetValue(sx, 0)
    vtklegend.UseBackgroundOn()
    vtklegend.SetBackgroundColor(vp.legendBC)
    vtklegend.SetBackgroundOpacity(0.6)
    vtklegend.LockBorderOn()
    vp.renderer.AddActor(vtklegend)


###########################################################################################
class Button:
    """
    Build a Button object to be shown in the rendering window.

    |buttons| |buttons.py|_
    """

    def __init__(self, fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle):
        """
        Build a Button object to be shown in the rendering window.
        """
        self._status = 0
        self.states = states
        self.colors = c
        self.bcolors = bc
        self.function = fnc
        self.actor = vtk.vtkTextActor()

        self.actor.GetActualPositionCoordinate().SetCoordinateSystemToNormalizedViewport()

        self.actor.SetPosition(pos[0], pos[1])
        #self.actor.SetDisplayPosition(pos[0], pos[1]) # pixel coords
        self.framewidth = 2
        self.offset = 5
        self.spacer = " "

        self.textproperty = self.actor.GetTextProperty()
        self.textproperty.SetJustificationToCentered()
        if font.lower() == "courier":
            self.textproperty.SetFontFamilyToCourier()
        elif font.lower() == "times":
            self.textproperty.SetFontFamilyToTimes()
        else:
            self.textproperty.SetFontFamilyToArial()
        self.textproperty.SetFontSize(size)
        self.textproperty.SetBackgroundOpacity(alpha)
        self.textproperty.BoldOff()
        if bold:
            self.textproperty.BoldOn()
        self.textproperty.ItalicOff()
        if italic:
            self.textproperty.ItalicOn()
        self.textproperty.ShadowOff()
        self.textproperty.SetOrientation(angle)
        self.showframe = hasattr(self.textproperty, "FrameOn")
        self.status(0)

    def status(self, s=None):
        """
        Set/Get the status of the button.
        """
        if s is None:
            return self.states[self._status]

        if isinstance(s, str):
            s = self.states.index(s)
        self._status = s
        self.textproperty.SetLineOffset(self.offset)
        self.actor.SetInput(self.spacer + self.states[s] + self.spacer)
        s = s % len(self.colors)  # to avoid mismatch
        self.textproperty.SetColor(getColor(self.colors[s]))
        bcc = np.array(getColor(self.bcolors[s]))
        self.textproperty.SetBackgroundColor(bcc)
        if self.showframe:
            self.textproperty.FrameOn()
            self.textproperty.SetFrameWidth(self.framewidth)
            self.textproperty.SetFrameColor(np.sqrt(bcc))
        return self

    def switch(self):
        """
        Change/cycle button status to the next defined status in states list.
        """
        self._status = (self._status + 1) % len(self.states)
        self.status(self._status)
        return self
